#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <assert.h>
#include <string.h>
#include "verilated.h"
#include "svdpi.h"
#include "macro.h"

#include "VMemory.h"
#include "VMemory___024root.h"

#define CONFIG_MSIZE 0x8000000
#define CONFIG_MBASE 0x80000000

enum MEM {
    MEM_LOAD = 0,
    MEM_STORE,
    MEM_DIRECT,
    __MEM_NUM
};

int rand_n(bool sign,int n) {
  int res = rand()%n;
  if(!sign && res < 0) res = -res;
  return res; 
}

void rand_op(VMemory *mem,enum MEM type) {
    mem->load_addr = 0;
    mem->load_bytes = 0;
    mem->load_sign = 0;
    mem->load_valid = 0;
    mem->store_addr = 0;
    mem->store_bytes = 0;
    mem->store_data = 0;
    mem->store_valid = 0;
    mem->dest_data_i = 0;
    mem->dest_i = 0;
    mem->dest_valid_i = 0;

    switch (type)
    {
    case MEM_LOAD:
        mem->dest_i = rand_n(false,1<<5);
        mem->dest_valid_i = 1;
        mem->load_valid = 1;
        mem->load_addr = CONFIG_MBASE + (uint64_t)rand_n(false,CONFIG_MSIZE);
        mem->load_bytes = 1 << rand_n(false,4);
        mem->load_sign = rand_n(false,1<<1);
        break;
    case MEM_STORE:
        mem->store_addr = CONFIG_MBASE + (uint64_t)rand_n(false,CONFIG_MSIZE);
        mem->store_bytes = 1 << rand_n(false,4);
        mem->store_data = rand();
        mem->store_valid = 1;
        break;
    case MEM_DIRECT:
        mem->dest_valid_i = 1;
        mem->dest_i = rand_n(false,1<<5);
        mem->dest_data_i = rand();
        break;
    default:
        assert(0);
        break;
    }
     
}
bool judgeOut(VMemory *mem) {
    bool ret_val = true;
    //judge load options
    if(mem->load_valid) {
        uint64_t laddr = mem->load_addr;
        bool sign = mem->load_sign;
        bool bytes = mem->load_bytes;
        bool load_data = mem->dcache_rdata;
        bool dest_data = mem->dest_data_o;
        if(mem->dcache_ren == 0) ret_val = false;
        if(mem->dcache_raddr != laddr) ret_val = false;
        if(mem->dest_valid_o == 0) ret_val = false;
        switch (bytes)
        {
        case 1: if(sign) load_data = SEXT(load_data,8);  else load_data = BITS(load_data,7,0);  break;
        case 2: if(sign) load_data = SEXT(load_data,16); else load_data = BITS(load_data,15,0); break;
        case 4: if(sign) load_data = SEXT(load_data,32); else load_data = BITS(load_data,31,0); break;
        case 8: if(sign) load_data = SEXT(load_data,64); else load_data = BITS(load_data,63,0); break;
        default: break;
        }
        if(load_data != dest_data) ret_val = false;
    } else if(mem->dest_valid_i) {
        if(mem->dest_valid_o == 0) ret_val = false;
        if(mem->dest_data_o != mem->dest_data_i) ret_val = false;
        if(mem->dest_o != mem->dest_i) ret_val = false;
    }
    if(mem->store_valid) {
        uint64_t saddr = mem->store_addr;
        uint8_t sbytes = mem->store_bytes;
        uint64_t sdata = mem->store_data;
        if(mem->dcache_wen == 0) ret_val = false;
        if(mem->dcache_waddr != saddr) ret_val = false;
        if(mem->dcache_wsize != sbytes) ret_val = false;
        if(mem->dcache_wdata != sdata) ret_val = false;
    }

    
    return ret_val;
}
void print_info(VMemory *mem) {
    printf("input:\n");
    printf("\tload:\n");
    printf("\t\taddr:0x%lx bytes:%d sign:%d valid:%d\n",mem->load_addr,mem->load_bytes,mem->load_sign,mem->load_valid);
    printf("\tstore\n");
    printf("\t\taddr:0x%lx data:0x%lx bytes:%d valid:%d\n",mem->store_addr,mem->store_data,mem->store_bytes,mem->store_valid);
    printf("\tdest\n");
    printf("\t\tdest:%d data:0x%lx valid:%d\n",mem->dest_i,mem->dest_data_i,mem->dest_valid_i);
    printf("output:\n");
    printf("\tdcache:\n");
    printf("\t\traddr:0x%lx rdata:0x%lx ren:%d\n",mem->dcache_raddr,mem->dcache_rdata,mem->dcache_ren);
    printf("\t\twaddr:0x%lx wdata:0x%lx wen:%d wsize:%d\n",mem->dcache_waddr,mem->dcache_wdata,mem->dcache_wen,mem->dcache_wsize);
    printf("\tdest\n");
    printf("\t\tdest:%d data:0x%lx valid:%d\n",mem->dest_o,mem->dest_data_o,mem->dest_valid_o);
}
int main(int argc, char **argv, char **env)
{
    Verilated::commandArgs(argc, argv);
    srand(time(0));
    VMemory *top = new VMemory;

#define test_cnt 1000000
    printf("inst memory test begin\n");
    struct timespec now;
    clock_gettime(CLOCK_MONOTONIC_COARSE, &now);
    uint64_t start_us = now.tv_sec * 1000000 + now.tv_nsec / 1000;
    bool pass = true;
    for (int i = 0; i < test_cnt; i++)
    {
        rand_op(top,(enum MEM)rand_n(false,__MEM_NUM));
        top->eval();
        if(judgeOut(top) == false) {
            print_info(top);
            pass = false;
        }
    }
    if(pass) printf("test pass\n");
    top->final();


    clock_gettime(CLOCK_MONOTONIC_COARSE, &now);
    uint64_t end_us = now.tv_sec * 1000000 + now.tv_nsec / 1000;
    printf("test cost time : %ldms\n",(end_us-start_us)/1000);
    printf("test num is %d\n",test_cnt);
    printf("test speed is %d op/ms\n",(int)(test_cnt/((end_us-start_us)/1000)));
    printf("inst memory test end\n");
    delete top;
    return 0;
}


#ifdef __cplusplus
extern "C" {
#endif
    long long mem_read(long long raddr, svBit is_icache) { return 0; }
    void mem_write(long long waddr, long long wdata, char wmask) { return; }
    void other_inst(svBit ecall, svBit fence, svBit ebreak) { return; }
    long long pmem_read(long long raddr, char rsize, char is_icache) { return 0; }
    void pmem_write(long long waddr, long long wdata, char wmask) { return; }
    void raise_hard_intr() {}
    void submit_inst(long long pc, int inst, long long next_pc) {}
    void difftest_memory_access(long long addr) {}
#ifdef __cplusplus
}
#endif